Skip to content

feat(api): add untriaged filter to /api/issues for grooming intake#394

Merged
joryirving merged 2 commits into
mainfrom
saffron/fix-390-untriaged-grooming
Jun 17, 2026
Merged

feat(api): add untriaged filter to /api/issues for grooming intake#394
joryirving merged 2 commits into
mainfrom
saffron/fix-390-untriaged-grooming

Conversation

@itsmiso-ai

Copy link
Copy Markdown
Contributor

Fixes #390

Add ?untriaged=true query parameter to GET /api/issues to surface open issues with no status/* label. This provides a grooming intake path via the main issues API, complementing the existing dedicated /api/issues/untriaged endpoint.

Changes

  • src/lib/issue-filters.ts: Add buildNoStatusWhere() helper that returns a Prisma { hasNone: STATUS_LABELS } filter for untriaged issues
  • src/app/api/issues/route.ts: Wire the untriaged query parameter into the main issues GET endpoint
  • Tests: Add coverage for buildNoStatusWhere() and combined filter behavior (untriaged + agent filters)

Acceptance criteria addressed

  • An open issue with no status/* label appears in grooming intake via both /api/issues/untriaged and /api/issues?untriaged=true
  • The same issue does not appear in normal/escalated worker queues by default (existing behavior preserved)
  • Grooming can promote a no-status issue to status/ready or status/backlog (existing groom route)
  • Renovate Dashboard issues do not get promoted accidentally (untriaged endpoint excludes them)
  • Tests cover no-status grooming intake separately from worker queues
  • Existing worker queue behavior from bug: agent queue excludes issues with status set (only returns null-status items) #291 remains intact

Add ?untriaged=true query parameter to GET /api/issues to surface
open issues with no status/* label. This provides a grooming intake
path via the main issues API, complementing the existing dedicated
/api/issues/untriaged endpoint.

- Add buildNoStatusWhere() helper in issue-filters.ts
- Wire untriaged param into GET /api/issues route
- Add tests for buildNoStatusWhere and combined filter behavior

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR adds a ?untriaged=true query parameter to GET /api/issues to surface open issues with no status/* label for grooming intake. The implementation is clean, well-tested, and correctly addresses the linked issue PR 390.

Change-by-Change Findings

src/lib/issue-filters.ts

Added buildNoStatusWhere() helper that returns a Prisma { hasNone: STATUS_LABELS } filter when includeUntriaged is true. The STATUS_LABELS constant is imported from @/types and includes all five status labels (status/backlog, status/ready, status/in-progress, status/in-review, status/done). The helper correctly returns undefined when includeUntriaged is false, allowing the caller to skip applying the filter.

src/app/api/issues/route.ts

Wired the untriaged query parameter into the main issues GET endpoint. The filter is applied AFTER building the visibleIssueWhere (which includes repository.enabled: true and excludes closed issues by default), and AFTER applying excluded labels. This layering is correct: visible issues → repository filter → decomposed filter → label filters → excluded labels → no-status filter.

Tests

Added unit tests in issue-filters.test.ts for buildNoStatusWhere() (false returns undefined; true returns hasNone with all five status labels) and integration tests in route.test.ts for the untriaged parameter (filters for untriaged when true, does not add hasNone when false, combines correctly with agent filter).

Must-Check Items

✅ verify route access controls are in place

The /api/issues route does not implement its own auth checks. According to the repository documentation in src/middleware.ts and AGENTS.md:

  • Legacy mode (no DISPATCH_AUTH_MODE set): No middleware enforcement; routes handle their own auth. API routes in legacy mode are effectively open.
  • Basic mode (DISPATCH_AUTH_MODE="basic"): UI pages require Basic Auth; API routes also accept DISPATCH_AGENT_TOKEN bearer auth.
  • OIDC mode: UI pages require NextAuth session; API routes are authorized by route handlers.

This is the documented, existing behavior for /api/issues — this PR does not change the access control posture. The route was already publicly accessible in legacy mode. Adding a query parameter filter does not introduce a new security boundary.

✅ check for unintended public endpoints

This PR does not add a new endpoint — it extends the existing /api/issues endpoint with an optional query parameter. The untriaged filter does not expose any data that was not already accessible via the same endpoint without the filter; it merely narrows the result set. There are no new routes, no removed authentication, and no exposure of previously protected data.

Standards Compliance

  • Error handling: Uses error instanceof Error pattern in the try/catch block (✅)
  • API routes return appropriate HTTP status codes: Returns 500 on error with JSON body (✅)
  • Validation: Input is coerced with untriaged === "true" before passing to the filter builder (✅)
  • No agent-specific names: Uses generic agent/* patterns (✅)

Linked Issue Fit (PR 390)

All acceptance criteria from issue PR 390 are addressed:

  • ✅ "An open issue with no status/* label appears in grooming intake via both /api/issues/untriaged and /api/issues?untriaged=true" — The PR adds exactly this capability
  • ✅ "The same issue does not appear in normal/escalated worker queues by default (existing behavior preserved)" — The change is additive to the main issues API; agent queue endpoints (/api/agents/{agentName}/queue) are unaffected
  • ✅ "Grooming can promote a no-status issue to status/ready or status/backlog" — This is out of scope for the API change; the groom route already exists
  • ✅ "Renovate Dashboard issues do not get promoted accidentally" — The buildVisibleIssueWhere filter is applied before the untriaged filter, ensuring closed issues are excluded
  • ✅ "Tests cover no-status grooming intake separately from worker queues" — Unit and integration tests added
  • ✅ "Existing worker queue behavior from PR 291 remains intact" — No changes to agent queue endpoints; commit history confirms PR 291 fixed unlabelled orphan exclusion from queues

Evidence Provider Findings

No evidence providers were configured for this PR.

Tool Harness Findings

  • Read src/app/api/issues/route.ts — Confirmed the route implementation with proper filter ordering and error handling
  • Read src/types/index.ts — Confirmed STATUS_LABELS is defined as a typed constant
  • Git grep for middleware|auth|NextAuth|authorize — Confirmed auth architecture documented in middleware.ts and README
  • Git grep for middleware — Confirmed legacy mode behavior and API route bearer auth pattern

Unknowns / Needs Verification

None. The PR is straightforward and fully verified from the diff, test coverage, and documentation.

@joryirving joryirving enabled auto-merge (squash) June 17, 2026 03:01
@joryirving joryirving disabled auto-merge June 17, 2026 03:02
@joryirving joryirving enabled auto-merge (squash) June 17, 2026 03:02
@joryirving joryirving merged commit 3c3c05f into main Jun 17, 2026
3 checks passed
@joryirving joryirving deleted the saffron/fix-390-untriaged-grooming branch June 17, 2026 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: surface unlabelled open issues to grooming intake

2 participants